IAMユーザのシークレットアクセスキーをSecret Managerに保管しローテーションさせてみた

IAMユーザのシークレットアクセスキーをSecret Managerに保管しローテーションさせてみた

IAMユーザのシークレットアクセスキーをSecret Managerに保管しローテーションさせる方法を紹介いたします。
Clock Icon2024.11.07

はじめに

プログラム実行に利用するIAMユーザのアクセスキーとシークレットアクセスキーを、半年に1度手動でローテーションしていました。手作業だと忘れがちということに加えて、セキュリティリスクもあるので、自動化しようと思いました。今回は、その具体的な構成とスクリプトについて紹介いたします。

前提条件

次を前提条件としています。

  • SAM(Serverless Application Model)がわかる
  • samコマンドを利用できる

構成について

概念図

  1. Secrets Managerのローテーション機能を利用し、Lambda関数を実行します。
  2. Lambda関数でIAMユーザのシークレットアクセスキーをローテーションさせます。
  3. 新しいシークレットアクセスキーをSecrets Managerに保存します。

全体の構成はこのようになっています。
Untitled

スケジュール起動=EventBridgeとなりがちですが、
Secrets Managerから直接Lambda関数を呼び出す機能で実装するとシンプルな構成になります。

必要な権限

Secrets ManagerがLambda関数を呼び出す権限

Secrets ManagerがLambda関数を呼び出す場合、リソースベースポリシーステートメントでSecrets Managerに次の権限を与える必要があります。

  • lambda:InvokeFunction

Lambda関数がIAMユーザのアクセスキーを編集できる権限

Lambda関数がIAMユーザのアクセスキーを編集するためには、次の権限が必要です。

  • iam:CreateAccessKey
  • iam:DeleteAccessKey
  • iam:UpdateAccessKey
  • iam:ListAccessKeys

Lambda関数がシークレットアクセスキーをSecrets Managerに保存する権限

Lambda関数がシークレットアクセスキーをSecrets Managerに保存するためには、次の権限が必要です。

  • secretsmanager:DescribeSecret
  • secretsmanager:GetSecretValue
  • secretsmanager:PutSecretValue
  • secretsmanager:UpdateSecretVersionStage
  • secretsmanager:ListSecretVersionIds

Lambda関数がCloudWatchにログを出力する権限

Lambda関数がCloudWatchにログを出力するには、次のマネージドポリシーが必要です。

  • AWSLambdaBasicExecutionRole

デプロイ

サンプルデータの注意点

説明のため、コードを簡単にする必要があります。実際にご利用の際は、適宜修正を加えてください。

  • 簡単のため、例外処理は入れていません。
  • IAMユーザの権限はサンプル用にバケットの一覧を取得できるようにしています。
  • IAMユーザ名はパラメータで取得するようにしています。

ファイル構成

構成は以下になるようにしてください。

IAMAccessKeyRotation
├── requirements.txt
├── template.yaml
└── src
  └── iamuser-secret-access-key-rotation-handler.py

Lambda関数

Lambda関数の役割は以下になっています。
簡単のため、例外処理は記載していませんが、必要であれば追記してください。

iamuser-secret-access-key-rotation-handler.py
import boto3
import json
import os

secretsmanager_client = boto3.client('secretsmanager')
iam_client = boto3.client('iam')

def handler(event, context):
    # 環境変数からシークレット名を取得
    username = os.environ["IAM_USER_NAME"]

    # イベントからデータを取得
    RotationToken = event['RotationToken']
    secret_arn = event['SecretId']

    # IAMユーザのアクセスキーをローテーションする
    updated_secret = rotate_iamuser_access_key(username)

    # Secrets Managerに新しいアクセスキーを保存
    save_secret_access_key_in_secret_manager(secret_arn,updated_secret,RotationToken)

def rotate_iamuser_access_key(username:str):
    """
    アクセスキーが2個ある場合、1つ削除後、新しく1つ作成する
    """
    # 2個ある場合、古い方のアクセスキーを削除
    # アクセスキーの一覧を取得
    existing_keys = iam_client.list_access_keys(UserName=username)['AccessKeyMetadata']

    # 2個あったら最も古いアクセスキーを削除
    if len(existing_keys) == 2:
        # min()を使い最古値を取得
        older_key = min(existing_keys, key=lambda x: x['CreateDate'])
        iam_client.delete_access_key(
            UserName=username, 
            AccessKeyId=older_key['AccessKeyId']
        )

    # IAMユーザで新しいアクセスキーを作成
    response = iam_client.create_access_key(UserName=username)
    new_access_key_id = response['AccessKey']['AccessKeyId']
    new_secret_access_key = response['AccessKey']['SecretAccessKey']
    print(f'ACCESS KEYを作成しました。')

    # シークレットを更新
    updated_secret = {
        "accessKey": new_access_key_id,
        "secretKey": new_secret_access_key
    }

    return updated_secret

def save_secret_access_key_in_secret_manager(secret_arn:str,updated_secret:dict,RotationToken:str):
    """
    Secrets Managerに新しいアクセスキーを保存
    """
    secretsmanager_client.put_secret_value(
        SecretId=secret_arn,
        SecretString=json.dumps(updated_secret),
        RotationToken=RotationToken
    )

モジュールについて

必要なモジュールはrequirements.txtにまとめています。

requirements.txt
boto3

SAMの構成について

以下のyamlファイルに構成をまとめています。
権限と構成については上に示した通りになっています。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for creating an IAM user and setting up Secrets Manager

Parameters:
  IAMUserName:
    Type: String
    Description: "The name of the IAM user whose access keys will be rotated."

Resources:
  IAMUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref IAMUserName
      Policies:
        - PolicyName: S3ListBucketsPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: 
                  - s3:ListAllMyBuckets
                Resource: "*"

  IAMUserSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub "${IAMUserName}-secret"
      Description: "Secret for IAM user access key"
      SecretString: "{}" 

  iamuserSecretAccessKeyRotationHandlerRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument: 
        Version: '2012-10-17'
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - lambda.amazonaws.com
            Action: 
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: SecretsManagerPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:DescribeSecret
                  - secretsmanager:GetSecretValue
                  - secretsmanager:PutSecretValue
                  - secretsmanager:UpdateSecretVersionStage
                  - secretsmanager:ListSecretVersionIds
                Resource: !Ref IAMUserSecret
        - PolicyName: IAMAccessKeyPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - iam:CreateAccessKey
                  - iam:DeleteAccessKey
                  - iam:UpdateAccessKey
                  - iam:ListAccessKeys
                Resource: !GetAtt IAMUser.Arn

  iamuserSecretAccessKeyRotationHandler:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: iamuser-secret-access-key-rotation-handler
      Handler: iamuser-secret-access-key-rotation-handler.handler
      Runtime: python3.9
      CodeUri: ./src
      Role: !GetAtt iamuserSecretAccessKeyRotationHandlerRole.Arn
      Environment:
        Variables:
          IAM_USER_NAME: !Ref IAMUserName

  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref iamuserSecretAccessKeyRotationHandler
      Action: lambda:InvokeFunction
      Principal: secretsmanager.amazonaws.com
      SourceArn: !Ref IAMUserSecret

  SecretRotationSchedule:
    Type: AWS::SecretsManager::RotationSchedule
    Properties:
      SecretId: !Ref IAMUserSecret
      RotationLambdaARN: !GetAtt iamuserSecretAccessKeyRotationHandler.Arn
      RotationRules:
        AutomaticallyAfterDays: 30

Outputs:
  RotationFunctionArn:
    Description: "ARN of the IAM Access Key Rotation Lambda function"
    Value: !GetAtt iamuserSecretAccessKeyRotationHandler.Arn

デプロイ

IAMAccessKeyRotationに移動後、以下のスクリプトを順番に実行していきます。
スクリプトを修正するたび、ビルドスクリプトから実行し直す必要があります。

sam build
sam deploy \
  --guided
  --capabilities CAPABILITY_NAMED_IAM \
  --profile <YOUR-PROFILE>
  • --guided: デプロイプロセスが対話型モードになり、初めてデプロイする際や設定を確認したいときに便利です。

  • --profile <YOUR-PROFILE> : AWS CLIプロファイルを指定します。

  • --capabilities CAPABILITY_NAMED_IAM : CloudFormationがIAMリソースを作成・変更するための権限を許可するためのオプションです。

実行確認

セキュリティ上の観点から、シークレットアクセスキーがローテーションしているところはお見せできませんが、アクセスキーがローテーションしていれば、シークレットアクセスキーもローテーションしています。

IAMユーザのシークレットキーが入れ替わっているか

ローテーション前のアクセスキー

スクリーンショット 2024-11-07 17.01.32

ローテーション後のアクセスキー

スクリーンショット 2024-11-07 17.03.03

結果

ローテーションが行われたことにより***********LQEEが削除され、***********IM7Fが追加されています。

IAMユーザのシークレットアクセスキーが新しくなっているか

ローテーション前

スクリーンショット 2024-11-07 17.04.40

ローテーション後

スクリーンショット 2024-11-07 17.06.05

結果

***********SEEMから***********IM7Fに変わった

バージョンが入れ替わっているか

ローテーション前のバージョン

スクリーンショット 2024-11-06 18.18.46

ローテーション後のバージョン

スクリーンショット 2024-11-06 18.20.45

結果

ローテーションされた結果、ローテーション前のAWSCURRENTAWSPREVIOUSにかわっています。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.